/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui;

import java.lang.reflect.InvocationTargetException;

import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.core.resources.RemoteResource;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.perforce.ui.internal.Policy;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.internal.BufferedResourceNode;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.DiffTreeViewer;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.compare.structuremergeviewer.IStructureComparator;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;

/**
  * @version $Revision: 240 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceCompareEditorInput extends CompareEditorInput
{
	// --------------------------------------------------------------------------- Private Constants
	
	/** Indicates that two nodes are equal. */
	private static final int NODE_EQUAL = 0;
	/** Indicates that two nodes are not equal. */
	private static final int NODE_NOT_EQUAL = 1;
	/** Indicates that it's unknown whether two nodes are equal or not. */
	private static final int NODE_UNKNOWN = 2;	

	// --------------------------------------------------------------------------- Private Variables
	
	/** Stores reference to the element displayed on the left side of the viewer. */
	private ITypedElement left;
	/** Stores reference to the element displayed on the right side of the viewer. */
	private ITypedElement right;
	/** Stores reference to the element displayed on the ancestor pane. */
	private ITypedElement ancestor;
	/** Stores the diff */
	private Object root;
	/** Stores the diff tree viewer. */
	private DiffTreeViewer diffViewer;
	
	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for PerforceCompareEditorInput.
	  * 
	  * @param left element displayed on the left.
	  * @param right element displayed on the right.
	  */
	public PerforceCompareEditorInput(ITypedElement left, ITypedElement right) 
	{
		this(left, right, null);
	}
	
	/** Constructor for PerforceCompareEditorInput.
	  * 
	  * @param left element displayed on the left.
	  * @param right element displayed on the right.
	  * @param ancestor element displayed in ancestor pane.
	  */
	public PerforceCompareEditorInput(
		ITypedElement left, ITypedElement right, ITypedElement ancestor) 
	{
		super(new CompareConfiguration());

		this.left = left;
		this.right = right;
		this.ancestor = ancestor;
	}	

	// ------------------------------------------------------------------------------ Public Methods

	/** @see CompareEditorInput#createDiffViewer(Composite) */
	public Viewer createDiffViewer(Composite parent) 
	{
		Viewer v = super.createDiffViewer(parent);
		if (v instanceof DiffTreeViewer)
		{
			diffViewer = (DiffTreeViewer) v;
		}
		
		return v;
	}
	
	/** @see CompareEditorInput#saveChanges(IProgressMonitor) */
	public void saveChanges(IProgressMonitor pm) 
		throws CoreException 
	{
		super.saveChanges(pm);
		if (root instanceof DiffNode) 
		{
			try 
			{
				commit(pm, (DiffNode) root);
			} 
			finally 
			{
				if (diffViewer != null)
				{
					diffViewer.refresh();
				}				
				setDirty(false);
			}
		}
	}
	
	// --------------------------------------------------------------------------- Protected Methods

	/** Sets the diff viewer.
	  *
	  * @param theDiffViewer the diff viewer instance. 
	  */
	protected void setDiffViewer(DiffTreeViewer theDiffViewer)
	{
		diffViewer = theDiffViewer;
	}

	/** @see CompareEditorInput#prepareInput(IProgressMonitor) */
	protected Object prepareInput(IProgressMonitor monitor)
		throws InvocationTargetException, InterruptedException
	{
		boolean threeWay = ancestor != null;
		if (right == null || left == null)
		{
			return null;
		}
			
		initLabels();
			
		Differencer differencer = new Differencer()
		{
			/** @see Differencer#contentsEqual(Object, Object) */
			protected boolean contentsEqual(Object input1, Object input2)
			{
				int compare = teamEqual(input1, input2);
				if (compare == NODE_EQUAL)
				{
					return true;
				}
				else
				if (compare == NODE_NOT_EQUAL) 
				{
					return false;
				}
				
				return super.contentsEqual(input1, input2);
			}
			
			/** @see Differencer#updateProgress(IProgressMonitor, Object) */
			protected void updateProgress(IProgressMonitor progressMonitor, Object node) 
			{
				super.updateProgress(progressMonitor, node);
				progressMonitor.worked(1);
			}
			
			/** @see Differencer#getChildren(Object) */
			protected Object[] getChildren(Object input) 
			{
				if (input instanceof IStructureComparator) 
				{
					Object[] children = ((IStructureComparator) input).getChildren();
					if (children != null)
					{
						return children;
					}
				}
				return null;
			}
			
			protected Object visit(
				Object data, int result, Object ancestor, Object left, Object right) 
			{
				return new InternalDiffNode2(
				(IDiffContainer) data, 
				result, 
				(ITypedElement) ancestor, 
				(ITypedElement) left, 
				(ITypedElement) right);
			}
		};
			
		try 
		{
			monitor.beginTask(Policy.bind("PerforceCompareEditorInput.comparing"), 30);
					
			IProgressMonitor sub = new SubProgressMonitor(monitor, 10);
			try 
			{
				sub.beginTask(Policy.bind("PerforceCompareEditorInput.comparing"), 100); 
				root = differencer.findDifferences(threeWay, sub, null, ancestor, left, right);
			
				if (root != null)
				{
					ResourceNode node = (ResourceNode) left;
					IResource resource = node.getResource();
					
					DiffNode result = (DiffNode) root;
					if (resource instanceof IContainer)
					{
						IDiffElement[] children = result.getChildren();
						result = new InternalDiffNode((IContainer) resource, result.getKind());
						for (int i = 0; i < children.length; i++)
						{
							result.add(children[i]);
						}
					}
					
					IContainer parent = null;					
					while ((parent = resource.getParent()) != null)
					{
						DiffNode tempResult = new InternalDiffNode(parent, result.getKind());
						tempResult.add(result);
						
						result = tempResult;
						resource = parent;
					}
					
					root = result;
				}
				
				return root;
			} 
			finally 
			{
				sub.done();
			}
		}
		catch (OperationCanceledException e) 
		{
			throw new InterruptedException(e.getMessage());
		}
		finally
		{
			monitor.done();
		}
	}

	/** Compares two nodes to determine if they are equal.  Returns NODE_EQUAL
	  * of they are the same, NODE_NOT_EQUAL if they are different, and
	  * NODE_UNKNOWN if comparison was not possible.
	  * 
	  * @param left the left object.
	  * @param right the right object.
	  * @return NODE_EQUAL, NODE_NOT_EQUAL or NODE_UNKNOWN
	  */
	protected int teamEqual(Object left, Object right) 
	{
		boolean threeWay = ancestor != null;
		
		ResourceNode localFileNode = null;
		if (threeWay)
		{
			if (right instanceof ResourceNode)
			{
				localFileNode = (ResourceNode) right;
			}
		}
		else
		{
			if (left instanceof ResourceNode)
			{
				localFileNode = (ResourceNode) left;
			}
		}
		
		if (localFileNode != null)
		{
			if (localFileNode.getResource().getType() == IResource.FILE)
			{
				LocalFile localFile = 
					(LocalFile) LocalResource.createFromResource(localFileNode.getResource());
					
				if (!localFile.isCheckedOut() && !localFile.isDirty() && !localFile.isOutOfDate())
				{
					return NODE_EQUAL;
				}
			}
		}
		
		if (left instanceof PerforceResourceEditionNode && 
		    right instanceof PerforceResourceEditionNode)
		{
			RemoteResource leftResource  = 
				((PerforceResourceEditionNode) left).getRemoteResource();
			RemoteResource rightResource = 
				((PerforceResourceEditionNode) right).getRemoteResource();
			
			ResourceSyncInfo leftSyncInfo = leftResource.getSyncInfo(null);
			ResourceSyncInfo rightSyncInfo = rightResource.getSyncInfo(null);
			
			if (leftSyncInfo != null && rightSyncInfo != null)
			{
				if (leftSyncInfo.getHaveRev() == rightSyncInfo.getHaveRev())
				{
					return NODE_EQUAL;
				}
			}
			else
			if (leftSyncInfo == null && 
			    (rightSyncInfo != null && 
			     rightSyncInfo.getHeadAction().equals(ResourceSyncInfo.ACTION_DELETE)))
			{
				return NODE_EQUAL;
			}
			else			
			{
				return NODE_UNKNOWN;
			} 			
		}
		
		return NODE_UNKNOWN;
	}

	// ----------------------------------------------------------------------------- Private Methods

	/** Recursively walks the diff tree and commits all changes. 
	  *
	  * @param pm the progress monitor
	  * @param node the diff node
	  * 
	  * @throws CoreException in case of an error. 
	  */
	private static void commit(IProgressMonitor pm, DiffNode node) 
		throws CoreException 
	{		
		ITypedElement left = node.getLeft();
		if (left instanceof BufferedResourceNode)
		{			
			((BufferedResourceNode) left).commit(pm);
		}
			
		IDiffElement[] children = node.getChildren();
		if (children != null) 
		{
			for (int i = 0; i < children.length; i++) 
			{
				IDiffElement element = children[i];
				if (element instanceof DiffNode)
				{
					commit(pm, (DiffNode) element);
				}
			}
		}
	}

	/**
	  * Sets up the title and pane labels for the comparison view.
	  */
	private void initLabels()
	{
		CompareConfiguration cc = (CompareConfiguration) getCompareConfiguration();
	
		String leftLabel = getLabel(left);
		cc.setLeftLabel(leftLabel);
		cc.setLeftImage(left.getImage());
	
		String rightLabel = getLabel(right);
		cc.setRightLabel(rightLabel);
		cc.setRightImage(right.getImage());
	
		String title = null;
		if (ancestor != null) 
		{
			cc.setAncestorLabel(getLabel(ancestor));
			cc.setAncestorImage(ancestor.getImage());
			
			Object[] arguments = new Object[] 
			{
				guessResourceName(), 
				getVersionLabel(ancestor), 
				getVersionLabel(left), 
				getVersionLabel(right)
			};
			
			title = Policy.bind("PerforceCompareEditorInput.titleAncestor", arguments);
		} 
		else 
		{
			String leftName = null;
			if (left != null) 
			{
				leftName = left.getName();
			}
				
			String rightName = null;
			if (right != null) 
			{
				rightName = right.getName();
			}
				
			if (leftName != null && !leftName.equals(rightName)) 
			{
				Object[] arguments = new Object[] 
				{
					leftName, 
					getVersionLabel(left), 
					rightName, 
					getVersionLabel(right)
				};
				
				title =
					 Policy.bind("PerforceCompareEditorInput.titleNoAncestorDifferent", arguments);
			}
			else 
			{
				Object[] arguments = new Object[] 
				{
					guessResourceName(), 
					getVersionLabel(left), 
					getVersionLabel(right)
				};
				
				title = Policy.bind("PerforceCompareEditorInput.titleNoAncestor", arguments);
			}
		}
		
		setTitle(title);
	}

	/** Returns the label for the given input element.
	  * 
	  * @param element the input element.
	  * @return the label.
	  */
	private String getLabel(ITypedElement element) 
	{
		return element.getName();
	}

	/** Returns a guess of the resource name being compared, for display
	  * in the title.
	  * 
	  * @return the guessed resourcename.
	  */
	private String guessResourceName() 
	{
		if (left != null)
		{
			return left.getName();
		}
		else
		if (right != null) 
		{
			return right.getName();
		}
		else
		if (ancestor != null) 
		{
			return ancestor.getName();
		}
		
		return ""; 
	}

	/** Returns the label for the given input element.
	  * 
	  * @param element the iput element
	  * @return the version label.
	  */
	private String getVersionLabel(ITypedElement element) 
	{
		if (element instanceof ResourceNode)
		{
			return Policy.bind("PerforceCompareEditorInput.workspaceLabel"); 
		}
		else		
		if (element instanceof PerforceResourceEditionNode) 
		{
			RemoteResource edition = ((PerforceResourceEditionNode) element).getRemoteResource();
			if (edition instanceof RemoteFolder)
			{
				return ((RemoteFolder) edition).getRevision();
			}
			else
			{
				return "#" + edition.getSyncInfo(null).getHeadRev();
			}
		}

		return element.getName();
	}
	
	// ------------------------------------------------------------------------------ Nested Classes
	
	/** Internal Diff Node. */
	static class InternalDiffNode extends DiffNode
	{		
		// ----------------------------------------------------------------------- Private Variables
		
		/** Stores the container. */
		private IContainer container;
		
		// --------------------------------------------------------------------- Public Constructors
		
		/** Constructor for InternalDiffNode. 
		  *
		  * @param theContainer associated container.
		  * @param kind kind value. 
		  */
		public InternalDiffNode(IContainer theContainer, int kind)
		{			
			super(kind);
			container = theContainer;
		}
		
		// -------------------------------------------------------------------------- Public Methods
		
		/** @see org.eclipse.compare.structuremergeviewer.DiffNode#getName() */
		public String getName() 
		{
			return container.getName();
		}
		
		/** @see org.eclipse.compare.structuremergeviewer.DiffNode#getImage() */
		public Image getImage() 
		{
			return CompareUI.getImage(container);
		}
	}
	
	/** Internal Diff Node */
	static class InternalDiffNode2 extends DiffNode implements IAdaptable
	{				
		// --------------------------------------------------------------------- Public Constructors
		
		/** Constructor for InternalDiffNode2. 
		  * 
		  * @see DiffNode#DiffNode(IDiffContainer, int, ITypedElement, ITypedElement, ITypedElement)
		  */
		public InternalDiffNode2(
			IDiffContainer parent, 
			int kind, 
			ITypedElement ancestor, 
			ITypedElement left, 
			ITypedElement right) 
		{
			super(parent, kind, ancestor, left, right);		
		}
		
		// -------------------------------------------------------------------------- Public Methods
		
		/** @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */
		public Object getAdapter(Class adapter) 
		{
			if (adapter == IResource.class)
			{
				ResourceNode resourceNode = (ResourceNode) getLeft();
				return resourceNode.getResource();
			}
			
			return null;
		}
	}
}
